#include "interpreter.h"
#include <cmath>
using namespace std;

bool VIP[5] = {false, false, false, false, false};

Frame::Frame(Frame *parent)
{
    this->parent = parent;
    bindings = {};
}

Procedure *empty = new Procedure;

void Frame::def(string name, Procedure *expr) //for value binding.
{
    bindings[name] = expr;
}

Frame *Frame::makeChild(vector<string> formals, vector<Procedure *> vals) //for function calling.
{
    Frame *child = new Frame(this);
    if (formals.size() != vals.size())
    {
        throw "Incorrect number of arguments to function call";
        return nullptr;
    }
    else
    {
        child = new Frame(this);
        for (int i = 0; i < formals.size(); i++)
        {
            child->def(formals[i], vals[i]);
        }
    }
    return child;
}

Procedure *Frame::lookup(string name)
{
    if (isNumber(name))
    {
        return new Var(name, this);
    }
    else if (bindings.find(name) != bindings.end())
    {

        return bindings[name];
    }
    else if (parent)
    {
        return parent->lookup(name);
    }
    else
    {
        throw "symbol " + name + " does not exist!";
    }
}

string gethead(string fish, Frame *env)
{
    removeSpace(fish);
    int i;
    int count = 0;
    for (i = fish.size() - 1; i >= 0; i--)
    {
        if (fish[i] == ')')
        {
            count++;
        }
        else if (fish[i] == '(')
        {
            count--;
        }
        if (!count)
            break;
    }
    return fish.substr(0, i);
}

double convertFromString(string str);

//replace threeEye expression

vector<string> cutAll(string fish, Frame *env)
{
    if (!contain(fish, ':') && !contain(fish, '?'))
        return cut(fish, env);
    vector<string> res;
    string temp;
    auto a = findEye(fish);
    if (a[3] - a[0] == fish.size() - 1)
    {
        res.push_back(fish);
        return res;
    }
    for (auto i : cut(fish.substr(0, a[0]), env))
    {
        res.push_back(i);
    }
    res.push_back(fish.substr(a[0], a[3] + 1 < fish.size() ? a[3] - a[0] + 1
                                                           : fish.npos));
    if (a[3] == fish.size() - 1)
        return res;
    temp = fish.substr(a[3] + 1, fish.npos);
    while (contain(temp, ':') || contain(temp, '?'))
    {
        auto a = findEye(temp);
        for (auto i : cut(temp.substr(0, a[0]), env))
        {
            //cout << endl
            // << "PushI :" << i << endl;
            res.push_back(i);
        }
        //cout << endl
        //<< "Push: " << temp.substr(a[0], a[3] + 1 < temp.size() ? a[3] - a[0] + 1 : temp.npos) << endl;
        res.push_back(temp.substr(a[0], a[3] + 1 < temp.size() ? a[3] - a[0] + 1
                                                               : temp.npos));
        if (a[3] == temp.size() - 1)
            return res;
        temp = temp.substr(a[3] + 1, temp.npos);
    }
    for (auto i : cut(temp, env))
    {
        res.push_back(i);
    }
    return res;
}

void singleThreeEye(string &src, int begin, int mid, int end, int end2, Frame *env)
{
    //cout << src.substr(begin + 1, mid - begin - 2) << endl;
    double a = cal(read(src.substr(begin + 1, mid - begin - 2), env), env);
    if (!(abs(a) < 1e-6))
    {
        replace_all_distinct(src, src.substr(begin, end2 - begin + 1 >= src.size() ? src.npos : end2 - begin + 1), src.substr(mid + 2, end - mid - 3));
    }
    else
    {
        replace_all_distinct(src, src.substr(begin, end2 - begin + 1 >= src.size() ? src.npos : end2 - begin + 1), src.substr(end + 2, end2 - end - 2));
    }
}
void threeEye(string &src, Frame *env)
{
    while (contain(src, '?') || contain(src, ':'))
    {
        //cout << src << endl;
        auto pos = findEye(src);
        singleThreeEye(src, pos[0], pos[1], pos[2], pos[3], env);
    }
}
//tokenize.
vector<string> cut(string fish, Frame *env)
{
    //cout << "Before three eye: " << fish << endl;
    removeSpace(fish);
    //threeEye(fish, env);
    //cout << "After three eye: " << fish << endl;
    vector<string> res;
    string temp = "";
    bool isFunc = false;
    bool funcPre = false;
    int number = 0;
    bool nega = true;
    for (auto i : fish)
    {
        if (isFunc && i == ')' && !number)
        {
            nega = false;
            temp += i;
            res.push_back(temp);
            temp = "";
            number = 0;
            isFunc = false;
            funcPre = false;
        }
        else if (isFunc)
        {
            if (i == '(')
            {
                number++;
            }
            else if (i == ')')
            {
                number--;
            }
            temp += i;
        }
        else if (i == '(' && funcPre)
        {
            temp += i;
            isFunc = true;
        }
        else if (IsOp(i))
        {

            funcPre = false;
            if (!temp.empty())
            {
                res.push_back(temp);
            }
            if ((i == '-') && nega)
            {
                res.push_back("0");
                temp = "";
                res.push_back(temp + i);
                nega = false;
            }
            else if ((i == '+') && nega)
            {
            }
            else
            {
                temp = "";
                res.push_back(temp + i);
                nega = false;
            }
            if (i != ')')
            {
                nega = true;
            }
            else
            {
                nega = false;
            }
        }
        else if (('0' <= i && i <= '9') || i == '.')
        {
            nega = false;
            temp += i;
        }
        else if (('a' <= i && i <= 'z') || ('A' <= i && i <= 'Z'))
        {
            nega = false;
            temp += i;
            funcPre = true;
        }
        else
        {
            throw "not valid expression: " + fish;
        }
    }
    if (temp.empty())
        return res;
    res.push_back(temp);
    return res;
}

//to find all symbols in a expr
vector<string> allSymbol(string fish, Frame *env)
{
    vector<string> temp = cutAll(fish, env);
    vector<string> symbs;
    for (auto i : temp)
    {
        if (!IsOp(i[0]) && !isNumber(i))
        {
            symbs.push_back(i);
        }
    }
    return symbs;
}

int purchase()
{
    cout << "Purchase for EXTENDED USE!\nVIP1 VIP2 VIP3 VIP4 VIP5\n$100 $200 $300 $400 $114514\nTO PAY>>> ";
    int i;
    cin >> i;
    int range = (i / 100) >= 5 ? 5 : i/100;
    for (int u = 0; u < range; u++)
    {
        VIP[u] = true;
    }
    cin.ignore();
    return range;
}

pair<string, vector<Procedure *>> formals(string s, Frame *env)
{
    removeSpace(s);
    if (!balance(s))
        throw "Not valid function calling:" + s;
    vector<Procedure *> res;
    string head = "";
    string temp;
    for (int i = 0; i < s.size(); i++)
    {
        if (s[i] != '(')
        {
            head += s[i];
            s.erase(i, 1);
            i--;
        }
        else
        {
            s.erase(i, 1);
            break;
        }
    }
    if (head == "purchase")
    {
        throw "Purchase Success! You are now VIP" + to_string(purchase());
    }
    if (s.empty())
        return make_pair(head, res);
    s.erase(s.size() - 1, 1);
    auto buffer = myGet(s, ',');
    for (auto temp : buffer)
    {
        if (env->bindings.find(temp) != env->bindings.end())
        {
            res.push_back(env->lookup(temp));
        }
        else
        {
            if (isFunction(temp))
            {
                auto head = gethead(temp, env);
                res.push_back(new Var(to_string(cal(read(temp, env), env)), env));
            }
            else
            {
                //cout << "begin" << endl;
                auto thunk = cal(read(temp, env), env);
                //cout << "end" << endl;
                if (isNumber(to_string(thunk)))
                {
                    res.push_back(new Var(to_string(thunk), env));
                }
                else
                {
                    throw "Var not found: " + temp;
                }
            }
        }
    }
    return make_pair(head, res);
}

//to build an anti-polland expression
stack<string> read(string line, Frame *env)
{
    stack<string> ops;
    stack<string> temp;
    vector<string> fish = cutAll(line, env);
    for (auto i : fish)
    {
        if (i == "(")
        {
            ops.push(i);
        }
        else if (i == ")")
        {
            if (ops.empty())
                throw "Hellish Input";
            while (ops.top() != "(")
            {
                temp.push(ops.top());
                ops.pop();
                if (ops.empty())
                {
                    throw "Hellish Input";
                }
            }
            ops.pop();
        }
        else if (i.size() == 1&&IsOp(i[0])  )
        {
            if (temp.empty())
                throw new exception;
            while (!ops.empty() && getPriority(ops.top()[0]) >= getPriority(i[0]))
            {
                temp.push(ops.top());
                ops.pop();
            }
            ops.push(i);
        }
        else
        {
            if (!isNumber(i) && !VIP[0] &&!beginWith(i,"purchase"))
            {
                throw "You are less than VIP1, you can only use Number!\nfor: " + i;
            }
            temp.push(i);
        }
    }

    while (!ops.empty())
    {
        temp.push(ops.top());
        ops.pop();
    }    
    //cout << "temp is: " << temp.top() << endl;
    return temp;
}

//to return a string for homo's reading
string read4Human(string line, Frame *env)
{
    stack<string> temp = read(line, env);
    string res = "";
    while (!temp.empty())
    {
        res = temp.top() + ',' + res;
        temp.pop();
    }

    return res;
}

//to convert a str into double, if str is invalid, return 0.
double convertFromString(string str)
{
    istringstream iss(str);
    double x;
    int y;
    if (iss >> x)
        return x;
    if (iss >> y)
        return y;
    //aout << "Convert Fail: " + str << endl;
    return 0.0;
}
vector<string> allSymbol(string fish, Frame *env);

Procedure *apply(Procedure *pro, vector<Procedure *> formals)
{
    auto newEnv = pro->makeCallFrame(formals);
    return eval(pro->_expr(), newEnv);
}

//to eval a stack.
double cal(stack<string> line, Frame *env)
{
    //cout << "here" <<endl;
    list<string> reverPolland;
    stack<string> temp = line;
    while (!temp.empty())
    {
        reverPolland.push_front(temp.top());
        temp.pop();
    }
    stack<string> bracket;
    for (auto i : reverPolland)
    {
        if (IsOp(i[0]) && i.size() == 1)
        {
            bool a;
            bool b = isFunction(bracket.top());
            if (b)
            {
                a = false;
            }
            else
            {
                if (env->bindings.find(bracket.top()) == env->bindings.end())
                {
                    if (isSymbol(bracket.top()))
                    {
                        a = !isNumber(env->lookup(bracket.top())->_expr());
                    }
                    else
                    {
                        bracket.top() = to_string(cal(read(bracket.top(), env), env));
                        a = !isNumber(env->lookup(bracket.top())->_expr());
                    }
                }
                else
                {
                    a = !isNumber(env->lookup(bracket.top())->_expr());
                }
                //cout << "here" + bracket.top() << endl;
            }
            double num2;
            if (!b)
            {
                num2 = a ? cal(read(env->lookup(bracket.top())->_expr(), env), env) : convertFromString(env->lookup(bracket.top())->_expr());
            }
            else
            {
                auto thunk = formals(bracket.top(), env);
                num2 = convertFromString(apply(env->lookup(thunk.first), thunk.second)->_expr());
                //num2 = cal(read(env->lookup(thunk.first)->_expr(), env->makeChild(env->lookup(thunk.first)->_formals(), thunk.second)), env->makeChild(env->lookup(thunk.first)->_formals(), thunk.second));
            }
            /*cout << "here" << endl
                 << env->lookup(bracket.top())->_expr() << endl;*/
            bracket.pop();
            if (bracket.empty())
                throw "Invalid expression";
            b = isFunction(bracket.top());
            if (b)
            {
                a = false;
            }
            else
            {
                if (env->bindings.find(bracket.top()) == env->bindings.end())
                {
                    if (isSymbol(bracket.top()))
                    {
                        a = !isNumber(env->lookup(bracket.top())->_expr());
                    }
                    else
                    {
                        bracket.top() = to_string(cal(read(bracket.top(), env), env));
                        a = !isNumber(env->lookup(bracket.top())->_expr());
                    }
                }
                else
                {
                    a = !isNumber(env->lookup(bracket.top())->_expr());
                }
            }
            double num1;
            if (!b)
            {
                num1 = a ? cal(read(env->lookup(bracket.top())->_expr(), env), env) : convertFromString(env->lookup(bracket.top())->_expr());
            }
            else
            {
                auto thunk = formals(bracket.top(), env);
                num1 = convertFromString(apply(env->lookup(thunk.first), thunk.second)->_expr());
                //num1 = cal(read(env->lookup(thunk.first)->_expr(), env->makeChild(env->lookup(thunk.first)->_formals(), thunk.second)), env->makeChild(env->lookup(thunk.first)->_formals(), thunk.second));
            }
            bracket.pop();
            double res = 0;
            if (i == "+")
            {
                res = num1 + num2;
            }
            else if (i == "-")
            {
                res = num1 - num2;
            }
            else if (i == "*")
            {
                res = num1 * num2;
            }
            else if (i == "/")
            {
                res = num1 / num2;
            }
            else if (i == ">")
            {
                if (!VIP[0])
                {
                    throw "You are less than VIP1, you cannot use >, <, ^, %!";
                }
                res = num1 > num2;
            }
            else if (i == "<")
            {
                if (!VIP[0])
                {
                    throw "You are less than VIP1, you cannot use >, <, ^, %!";
                }
                res = num1 < num2;
            }
            else if (i == "^")
            {
                if (!VIP[0])
                {
                    throw "You are less than VIP1, you cannot use >, <, ^, %!";
                }
                res = pow(num1, num2);
            }
            else if (i == "%")
            {
                if (!VIP[0])
                {
                    throw "You are less than VIP1, you cannot use >, <, ^, %!";
                }
                res = (int)num1 % (int)num2;
            }
            else
            {
                throw "Invalid expression";
            }
            bracket.push(to_string(res));
        }
        else
        {
            threeEye(i, env);
            //cout << i << endl;
            bracket.push(i);
            //cout << i << endl;
        }
    }
    if (bracket.empty())
        return 0;
    if (isFunction(bracket.top()))
    {
        //cout << "Function DA⭐ZE!" << endl;
        auto thunk = formals(bracket.top(), env);
        //cout << "Function DA⭐YO!" << endl;
        return cal(read(env->lookup(thunk.first)->_expr(), env->makeChild(env->lookup(thunk.first)->_formals(), thunk.second)), env->makeChild(env->lookup(thunk.first)->_formals(), thunk.second));
    }
    else if (!isNumber(bracket.top()))
    {
        if (env->bindings.find(bracket.top()) != env->bindings.end())
        {
            return cal(read(env->lookup(bracket.top())->_expr(), env), env);
        }
        else
        {
            if (isSymbol(bracket.top()))
            {
                return cal(read(env->lookup(bracket.top())->_expr(), env), env);
            }
            else
            {
                if (looseBalance(bracket.top()))
                    //bracket.top() = to_string(eval(read(bracket.top(), env), env));
                    return cal(read(env->lookup(to_string(cal(read(bracket.top(), env), env)))->_expr(), env), env);
                return cal(read(env->lookup(bracket.top())->_expr(), env), env);
            }
        }
    }
    return convertFromString(env->lookup(bracket.top())->_expr());
}

Procedure *eval(string input, Frame *env)
{
    
    if (input == "I am your father")
    {
        for (auto &i : VIP)
        {
            i = true;
        }
        throw "All VIP unlocked!";
    }
    
    if (!looseBalance(input))
    {
        throw "Bracket not match:" + input;
    }
    string copy = input;
    removeSpace(copy);
    if (copy.empty())
    {
        return empty;
    }
    //Assume it as a Var
    if (copy == "SHOWVAR")
    {
        for (auto i : env->bindings)
        {
            if (i.second->typeIs("Var"))
            {
                cout << i.first << ": " << i.second->_expr() << endl;
            }
        }
        return empty;
    }
    else if (copy == "SHOWFUNC")
    {
        for (auto i : env->bindings)
        {
            if (i.second->typeIs("Func"))
            {
                cout << i.first << "(";
                for (auto j : i.second->_formals())
                {
                    cout << j << ", ";
                }
                cout << (!i.second->_formals().empty() ? "\b\b)" : ")")
                     << ": " << i.second->_expr() << endl;
            }
        }
        return empty;
    }
    else if (copy == "exit")
    {
        exit(0);
    }
    if (beginWith(input, "Var"))
    {
        if (!VIP[2])
        {
            throw "You are less than VIP3, you cannot use Variable!";
        }
        removeHeadSpace(input);
        input.erase(0, 3);
        removeSpace(input);
        if (contain(input, '='))
        {
            stringstream buffer(input);
            string temp;
            string s1, s2;
            getline(buffer, s1, '=');
            getline(buffer, s2, '=');
            if (s1.empty() || s2.empty())
            {
                throw "Invalid assignment: Var " + input;
            }
            else if (beginWith(s1,"purchase"))
            {
                throw "You cannot override function purchase!";
            }
            else
            {

                //cout << s1 << '=' + s2 << endl;
                env->def(s1, new Var(s2, env));
                return env->lookup(s1);
            }
        }
        else
        {
            throw "Invalid assignment: " + input;
        }
    } //Assume it as a Func
    else if (beginWith(input, "Func"))
    {
        if (!VIP[3])
        {
            throw "You are less than VIP4, you cannot use Function!";
        }
        removeHeadSpace(input);
        input.erase(0, 4);
        if (contain(input, '='))
        {
            stringstream buffer(input);
            string temp;
            string s1, s2;
            getline(buffer, s1, '=');
            removeSpace(s1);
            getline(buffer, s2);
            removeHeadSpace(s2);
            if (s1.empty() || s2.empty())
            {
                throw "Invalid assignment: Func " + input;
            }
            else if (beginWith(s1,"purchase"))
            {
                throw "You cannot override function purchase!";
            }
            else
            {
                auto thunk = gethead(s1, env);
                //cout << "Head is " << thunk << endl;
                //cout << s1 << '=' + s2 << endl;
                env->def(thunk, new Function(s2, pureFormals(s1), env));
                return env->lookup(thunk);
            }
        }
        else
        {
            throw "Invalid assignment: Function " + input;
        }
    }
    else if (contain(input, '='))
    {
        if (!VIP[1])
        {
            throw "You are less than VIP2, you cannot use Assignment!";
        }
        //removeSpace(input);
        stringstream buffer(input);
        string temp;
        string s1, s2;
        getline(buffer, s1, '=');
        removeSpace(s1);
        getline(buffer, s2, '=');
        if (s1.empty() || s2.empty())
        {
            throw "Invalid assignment: " + input;
        }
        else if (s1 == "purchase")
        {
            throw "You cannot override function purchase!";
        }
        else
        {
            if (env->lookup(s1)->typeIs("Func"))
            {
                auto thunk = allSymbol(s2, env);
                env->def(s1, new Function(s2, env->lookup(s1)->_formals(), env));
                return env->lookup(s1);
            }
            else
            {
                //cout << "s1:" << s1 << " s2:" << s2 << endl;
                auto symbs = allSymbol(s2, env);
                for (auto i : symbs)
                {
                    if (env->bindings.find(i) != env->bindings.end())
                    {
                        replace_all_distinct(s2, i, to_string(cal(read(env->lookup(i)->_expr(), env), env)));
                    }
                    else
                    {
                        throw "symbol " + i + " does not exist!";
                    }
                }

                //cout << s1 << '=' + s2 << endl;
                env->def(s1, new Var(s2, env));
                return env->lookup(s1);
            }
        }
    }
    //else assume it as expr
    else
    {
        if (isSymbol(input))
        {
            if (!VIP[0] && input != "purchase")
            {
                throw "You are less than VIP1, you cannot use Symbol!";
            }
            if (env->lookup(input)->typeIs("Func"))
            {
                return env->lookup(input);
            }
            else
            {
                return new Var(to_string(cal(read(input, env), env)), env);
            }
        }
        else
        {
            return new Var(to_string(cal(read(input, env), env)), env);
        }
    }
}